#!/usr/bin/perl

# The MIT License (MIT)

# Copyright (c) 2013 Osemwaro Pedro

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

use File::Basename;
use Time::Local;
use POSIX;
use open qw(:std :utf8);

my $scriptName=fileparse($0);
my $projURL="http://goo.gl/gvObX";

if(@ARGV!=1) {
  print <<ENDDOC;
USAGE: $scriptName <SCHEDULE FILE>

This file parses a schedule file formatted like 2013-schedule.txt, and outputs 
its events in the iCalendar format.
ENDDOC

  exit 0;
}


open FILE, "<", $ARGV[0];

my @prevLns;
my @prevEvntSmmry;
my %monthNos=(
  July=>"07",
  Aug=>"08",
  Sept=>"09"
);

print <<ENDDOC;
BEGIN:VCALENDAR
VERSION:2.0
ENDDOC

my $bbcSrcURL;
while(<FILE>) {
  if(/#src:(.*)/) {
    $bbcSrcURL=$1;
    next;
  }
  s/\s*(.*)\s*/$1/;
  if(my @evntSmmry=
      /(\w+)\s+(\d+)\s+(\w+)\s+(\d+)\s*,\s*(\d+\.\d+\w+)\s*,\s*([\w\s]+)/) 
  {
    if(@prevEvntSmmry) {
      my $newTitle;
      if(@prevLns) {
        $newTitle=pop @prevLns;
      } else {
        die "Error: consecutive event summaries in proms schedule\n";
      }
       writeEvent(\@prevEvntSmmry,\@prevLns);
       
       @prevLns=($newTitle);
    }
    @prevEvntSmmry=@evntSmmry;
  } else {
    push @prevLns, $_;
  }
}

writeEvent(\@prevEvntSmmry,\@prevLns);

print "END:VCALENDAR\n";
close FILE;

# Event summary is: (Day,Date,Month,Year,Time,Place)
sub writeEvent() {
  my ($s,$details)=@_[0,1];
  
  die "Event summary contained unexpected month name: $s->[2]\n"
    if(!grep {$_ eq $s->[2]} keys %monthNos);
    
  my $summary=shift @$details;
  $summary=fmtText("SUMMARY:$summary");
    
  my $monthNo=$monthNos{$s->[2]};
  my $t=parseTime($s->[4]);
  my $durSecs=$summary=~/Proms Chamber Music/ ? 3600 : estDuration($details);
  # Ignore suspiciously short durations
  $durSecs=0 if($durSecs<3600);
  
  # @et=end (hour,min,sec)
  # @ed=end (year,month,date)
  my @et;
  my @ed;
  my $stT=timelocal(0, $t->[1], $t->[0], $s->[1], $monthNo-1, $s->[3]);
  my $maxSecLen=60*60;
  my $noIntervals=floor($durSecs/$maxSecLen);
  my $intervalLen=20*60;
  $noIntervals-=$durSecs % $maxSecLen<$maxSecLen/2 if($noIntervals);
  # Round end time to a 5 minute interval
  my $roundT=5*60;
  my $endT=floor(($stT+$durSecs+$noIntervals*$intervalLen)/$roundT+0.5)*$roundT;    
  (undef,$et[1],$et[0],$ed[2],$ed[1],$ed[0],undef,undef,undef) =
    localtime($endT);
  
  $ed[2]=pad2Digits($ed[2]);
  $ed[1]=pad2Digits($ed[1]+1);
  $ed[0]+=1900;
  
  $et[0]=pad2Digits($et[0]);
  $et[1]=pad2Digits($et[1]);
  my $endTimeStr="\nDTEND:$ed[0]$ed[1]$ed[2]\T$et[0]$et[1]00";
  
  my $startDate=pad2Digits($s->[1]);
  my $loc=fmtText("LOCATION:$s->[5]");
  my $disclaimer1=length($bbcSrcURL) ? 
    "approximations based on the incomplete information provided in $bbcSrcURL "
    : "approximate";
  my $disclaimer=
    "DISCLAIMER: the concert end times in this calendar are $disclaimer1.\nFor".
    " more accurate end times, please see: ".
    genDayURL($s->[3],$monthNo,$startDate)." .\n\n".
    "iCalendar data generated by $projURL .";
  my $desc=@$details ?
    fmtText("DESCRIPTION:".join("\n",@$details)."\n\n$disclaimer") :
    fmtText("DESCRIPTION:$disclaimer");
  
  print <<ENDDOC
BEGIN:VEVENT
DTSTART:$s->[3]$monthNo$startDate\T$t->[0]$t->[1]$t->[2]$endTimeStr
UID:
$summary
$loc
$desc
END:VEVENT
ENDDOC

}

sub estDuration() {
  my $details=$_[0];
  my $dur=0;
  my $interTime=5;
  my $details=join("",@$details);
  while($details=~/\(\s*c?\s*(\d+)\s*mins\s*\)/g) {
    $dur+=$dur ? $interTime+$1 : $1;
  }
  60*$dur;
}

sub parseTime() {
  $_[0]=~/(\d+)\.(\d+)(\w+)/;
  die "Unexpected date format: $_[0]\n" if($3 ne "am" && $3 ne "pm");
  
  [$1 % 12+($3 eq "pm" ? 12 : 0),$2,"00"];
}

sub pad2Digits() {
  $_[0]<10 ? "0$_[0]" : $_[0];
}

sub fmtText() {
  $_[0]=~s/\\/\\\\/g;
  $_[0]=~s/,/\\,/g;
  $_[0]=~s/;/\\;/g;
  $_[0]=~s/\n/\\n/g;
  
  my @lines=($_[0]);
  my $maxL=75;
  while(length($lines[-1])>$maxL) {
    my $s=pop @lines;
    push @lines, substr($s,0,$maxL), " ".substr($s,$maxL);
  }
  
  join("\r\n",@lines);
}

sub genDayURL {
  my ($year,$month,$date)=@_;
  my %monthNames=(
    7=>"july", 8=>"august", 9=>"september"
  );
  "http://www.bbc.co.uk/proms/whats-on/$year/$monthNames{$month+0}-$date";
}
